import { Queue } from "../../../Support/Share/Lib/Collections/Queue";
import { Dictionary } from "../../../Support/Share/Lib/Dictionary";
import { UnOrderMultiMap } from "../../../Support/Share/Lib/UnOrderMultiMap";
import { Singleton } from "../../Singleton/Singleton";
import { Entity } from "../Entity/Entity";
import { AEvent, EventStruct, IEvent } from "./IEvent";
import { IAwake4System, IAwake3System, IAwake2System, IAwake1System, IAwakeSystem } from "./IAwakeSystem";
import { IDestroySystem } from "./IDestroySystem";
import { ILateUpdateSystem } from "./ILateUpdateSystem";
import { ILoadSystem } from "./ILoadSystem";
import { InstanceQueueIndex } from "./InstanceQueueIndex";
import { ISystemType } from "./ISystemType";
import { IUpdateSystem } from "./IUpdateSystem";
import { Scene } from "../Entity/Scene";
import { SceneType } from "../Entity/SceneType";
import { AInvokeHandler, AInvokeHandler1, IInvoke } from "./IInvoke";
import { HashSet } from "../../../Support/Share/Lib/Collections/HashSet";
import { Log } from "../Log/Log";
import { DumpObject, EventAttrData, EventAttribute, GetCustomAttributes, GetTypeOf, ImplementsInterface, InvokeAttrData, InvokeAttribute, ObjectSystemAttribute, Type, TypeOf, ValidParam } from "../../../Decorator/Decorator";
import { ISingletonLateUpdate } from "../../Singleton/ISingletonLateUpdate";
import { ISingletonUpdate } from "../../Singleton/ISingletonUpdate";


class OneTypeSystems {
    readonly map = new UnOrderMultiMap<string, any>();
    readonly queueFlag: boolean[] = new Array(InstanceQueueIndex.Max);
}

class TypeSystems {

    readonly typeSystemsMap = new Dictionary<string, OneTypeSystems>();

    public GetOrCreateOneTypeSystems(type: string) {
        let systems = this.typeSystemsMap.get(type);
        if (systems != null) {
            return systems;
        }

        systems = new OneTypeSystems();
        this.typeSystemsMap.Add(type, systems);
        return systems;
    }

    public GetOneTypeSystems(type: string) {
        let systems = this.typeSystemsMap.get(type);
        return systems;
    }

    public GetSystems(type: string, systemType: string) {
        let oneTypeSystems = this.typeSystemsMap.get(type)
        if (!oneTypeSystems) {
            return null;
        }

        let systems = oneTypeSystems.map.get(systemType);
        if (!systems) {
            return null;
        }

        return systems;
    }

}

export class EventInfo {

    private _IEvent: IEvent;
    public get IEvent() {
        return this._IEvent;
    }

    private _SceneType: SceneType;
    public get SceneType(): SceneType {
        return this._SceneType;
    }

    constructor(iEvent: IEvent, sceneType: SceneType) {
        this._IEvent = iEvent;
        this._SceneType = sceneType;
    }

}

@ImplementsInterface(ISingletonUpdate)
@ImplementsInterface(ISingletonLateUpdate)
export class EventSystem extends Singleton<EventSystem> implements ISingletonUpdate, ISingletonLateUpdate {

    private static _Instance: EventSystem;
    static get Instance() {
        return this._Instance;
    }

    protected set Instance(value: EventSystem) {
        EventSystem._Instance = value;
    }

    private readonly types = new Dictionary<Type, HashSet<Type>>();

    private readonly allTypes = new Dictionary<string, Type>();

    private readonly allEntities = new Dictionary<number, Entity>();

    private readonly queues = new Queue<number[]>(InstanceQueueIndex.Max);

    private readonly allEvents = new Dictionary<string, Array<EventInfo>>();

    private allInvokes = new Dictionary<Type, Dictionary<number, Object>>();

    private typeSystems = new TypeSystems();

    constructor() {
        super();

        let len = this.queues.Count;
        for (let i = 0; i < len; i++) {
            this.queues[i] = [];
        }
    }

    Add(addTypes: Dictionary<string, any>) {
        // console.log(`EventSystem Add Types.`);

        // console.log(addTypes);

        // console.log(`EventSystem Parse Types.`);

        this.allTypes.clear();
        this.allEvents.clear();

        this.typeSystems = new TypeSystems();

        // console.log(`EventSystem All Types.`);
        for (let key in addTypes) {
            // 记录所有的有BaseAttribute标记的的类型
            let objects = addTypes[key];
            this.types.Add(key, objects);
        }
        // console.log(this.types);
        // console.log(`EventSystem All Types.`);

        let objectSystemTypes = addTypes[GetTypeOf(ObjectSystemAttribute)];
        for (let type in objectSystemTypes) {
            let clazz = objectSystemTypes[type];
            let iSystemType = new clazz() as ISystemType;
            let iSystemTypeParent = Object.getPrototypeOf(iSystemType);
            let tempType = iSystemType.Type() || iSystemTypeParent.Type();
            if (!tempType) {
                continue;
            }
            let tempSystemType = iSystemType.SystemType();
            let oneTypeSystems = this.typeSystems.GetOrCreateOneTypeSystems(tempType);
            oneTypeSystems.map.add(tempSystemType, iSystemType);

            let index = iSystemType.GetInstanceQueueIndex();
            if (index > InstanceQueueIndex.None && index < InstanceQueueIndex.Max) {
                oneTypeSystems.queueFlag[index] = true;
            }

            // console.log(`==================== Add ObjectSystemAttribute ====================`);
            // console.log(index);
            // console.log(tempType);
            // console.log(tempSystemType);
            // console.log(iSystemType);
            // console.log(oneTypeSystems);
        }
        // console.log(this.typeSystems);

        let eventTypes = addTypes[GetTypeOf(EventAttribute)];
        for (let type in eventTypes) {
            let clazz = eventTypes[type];
            let iEvent = new clazz() as IEvent;
            if (iEvent == null) {
                throw new Error(`type not is AEvent: ${clazz.Name}`);
            }
            let iEventParent = Object.getPrototypeOf(iEvent);
            let attrs = GetCustomAttributes(clazz);
            for (let attr of attrs) {
                let eventAttribute = attr as EventAttrData;
                let eventType = iEvent.Type || iEventParent.Type;
                let eventInfo = new EventInfo(iEvent, eventAttribute.SceneType);
                if (!this.allEvents.ContainsKey(eventType)) {
                    this.allEvents.Add(eventType, []);
                }
                this.allEvents.TryGetValue(eventType).push(eventInfo);

                // console.log(eventType);
                // console.log(eventAttribute);
            }

            // console.log(`==================== Add EventAttribute ====================`);
            // console.log(iEvent);
            // console.log(attrs);
        }
        // console.log(this.allEvents);

        this.allInvokes = new Dictionary<Type, Dictionary<number, Object>>();
        let invokeTypes = addTypes[GetTypeOf(InvokeAttribute)];
        for (let type in invokeTypes) {
            let clazz = invokeTypes[type];
            let iInvoke = new clazz() as IInvoke;
            if (iInvoke == null) {
                throw new Error(`type not is callback: ${clazz.Name}`);
            }
            let iInvokeParent = Object.getPrototypeOf(iInvoke);
            let attrs = GetCustomAttributes(clazz);
            for (let attr of attrs) {
                let dict = this.allInvokes.TryGetValue(iInvoke.Type);
                if (!dict) {
                    dict = new Dictionary<number, Object>();
                    this.allInvokes.Add(iInvoke.Type || iInvokeParent.Type, dict);
                }
                let invokeAttribute = attr as InvokeAttrData;
                try {
                    dict.Add(invokeAttribute.Type, iInvoke);
                } catch (e) {
                    throw new Error(`action type duplicate: ${iInvoke.Type} ${invokeAttribute.Type}`);
                }
            }
        }
        // console.log(this.allInvokes);
    }

    GetTypes(systemAttributeType: Type) {
        if (!this.types.ContainsKey(systemAttributeType)) {
            return new HashSet<Type>();
        }
        return this.types.TryGetValue(systemAttributeType);
    }

    GetType(typeName: string): Type {
        return this.allTypes[typeName];
    }

    RegisterSystem(component: Entity, isRegister: boolean = true) {
        if (!isRegister) {
            this.Remove(component.InstanceId);
            return;
        }

        this.allEntities.Add(component.InstanceId, component);

        // console.log("==================== EventSystem.registerSystem ====================");
        // console.log(this.allEntities);
        // console.log("==================== EventSystem.registerSystem ====================");

        let type = component.clazzType;
        let oneTypeSystems = this.typeSystems.GetOneTypeSystems(type);
        if (oneTypeSystems == null) {
            return;
        }
        let len = oneTypeSystems.queueFlag.length;
        for (let i = 0; i < len; ++i) {
            if (!oneTypeSystems.queueFlag[i]) {
                continue;
            }
            this.queues[i].push(component.InstanceId);
        }
    }

    Remove(instanceId: number) {
        this.allEntities.delete(instanceId);
    }

    Get(instanceId: number) {
        let component = this.allEntities.TryGetValue(instanceId);
        return component;
    }

    IsRegister(instanceId: number) {
        return this.allEntities.has(instanceId);
    }

    Awake<T extends Entity>(component: T): void;
    Awake<T extends Entity, A>(component: T, a: A): void;
    Awake<T extends Entity, A, B>(component: T, a: A, b: B): void;
    Awake<T extends Entity, A, B, C>(component: T, a: A, b: B, c: C): void;
    Awake<T extends Entity, A, B, C, D>(component: T, a: A, b: B, c: C, d: D): void;
    Awake<T extends Entity, A, B, C, D>(component: T, a?: A, b?: B, c?: C, d?: D): void {
        let aValid = ValidParam(a);
        let bValid = ValidParam(b);
        let cValid = ValidParam(c);
        let dValid = ValidParam(d);
        if (aValid && bValid && cValid && dValid) {
            let iAwakeSystems: IAwake4System<A, B, C, D>[] = this.typeSystems.GetSystems(component.GetClassType(), TypeOf(IAwake4System));
            if (iAwakeSystems == null) {
                return;
            }

            for (let iAwakeSystem of iAwakeSystems) {
                if (iAwakeSystem == null) {
                    continue;
                }

                try {
                    iAwakeSystem.Run(component, a, b, c, d);
                } catch (e) {
                    Log.Error(e);
                }
            }
        } else if (aValid && bValid && cValid) {
            let iAwakeSystems: IAwake3System<A, B, C>[] = this.typeSystems.GetSystems(component.GetClassType(), TypeOf(IAwake3System));
            if (iAwakeSystems == null) {
                return;
            }

            for (let iAwakeSystem of iAwakeSystems) {
                if (iAwakeSystem == null) {
                    continue;
                }

                try {
                    iAwakeSystem.Run(component, a, b, c);
                } catch (e) {
                    Log.Error(e);
                }
            }
        } else if (aValid && bValid) {
            let iAwakeSystems: IAwake2System<A, B>[] = this.typeSystems.GetSystems(component.GetClassType(), TypeOf(IAwake2System));
            if (iAwakeSystems == null) {
                return;
            }

            for (let iAwakeSystem of iAwakeSystems) {
                if (iAwakeSystem == null) {
                    continue;
                }

                try {
                    iAwakeSystem.Run(component, a, b);
                } catch (e) {
                    Log.Error(e);
                }
            }
        } else if (aValid) {
            let iAwakeSystems: IAwake1System<A>[] = this.typeSystems.GetSystems(component.GetClassType(), TypeOf(IAwake1System));
            if (iAwakeSystems == null) {
                return;
            }

            for (let iAwakeSystem of iAwakeSystems) {
                if (iAwakeSystem == null) {
                    continue;
                }

                try {
                    iAwakeSystem.Run(component, a);
                } catch (e) {
                    Log.Error(e);
                }
            }
        } else {
            let iAwakeSystems: IAwakeSystem[] = this.typeSystems.GetSystems(component.GetClassType(), TypeOf(IAwakeSystem));
            if (iAwakeSystems == null) {
                return;
            }

            for (let iAwakeSystem of iAwakeSystems) {
                if (iAwakeSystem == null) {
                    continue;
                }

                try {
                    iAwakeSystem.Run(component);
                } catch (e) {
                    Log.Error(e);
                }
            }
        }
    }

    Load() {
        let queue = this.queues[InstanceQueueIndex.Load];
        let count = queue.length;
        while (count-- > 0) {
            let instanceId = queue.shift();
            let component = this.allEntities.get(instanceId);
            if (!component) {
                continue;
            }

            if (component.IsDisposed) {
                continue;
            }

            let iLoadSystems: ILoadSystem[] = this.typeSystems.GetSystems(component.GetClassType(), TypeOf(ILoadSystem));
            if (iLoadSystems == null) {
                continue;
            }

            queue.push(instanceId);

            for (let iLoadSystem of iLoadSystems) {
                try {
                    iLoadSystem.Run(component);
                } catch (e) {
                    Log.Error(e);
                }
            }
        }
    }

    DestoryEntity(component: Entity) {
        let iDestroySystems: IDestroySystem[] = this.typeSystems.GetSystems(component.GetClassType(), TypeOf(IDestroySystem));
        if (iDestroySystems == null) {
            return;
        }

        for (let iDestroySystem of iDestroySystems) {
            if (iDestroySystem == null) {
                continue;
            }

            try {
                iDestroySystem.Run(component);
            } catch (e) {
                Log.Error(e);
            }
        }
    }

    Update() {
        let queue = this.queues[InstanceQueueIndex.Update];
        let count = queue.length;
        while (count-- > 0) {
            let instanceId = queue.shift();
            let component = this.allEntities.get(instanceId);
            if (!component) {
                continue;
            }

            if (component.IsDisposed) {
                continue;
            }

            let iUpdateSystems: IUpdateSystem[] = this.typeSystems.GetSystems(component.GetClassType(), TypeOf(IUpdateSystem));
            if (iUpdateSystems == null) {
                continue;
            }

            queue.push(instanceId);

            for (let iUpdateSystem of iUpdateSystems) {
                try {
                    iUpdateSystem.Run(component);
                } catch (e) {
                    Log.Error(e);
                }
            }
        }
    }

    LateUpdate() {
        let queue = this.queues[InstanceQueueIndex.LateUpdate];
        let count = queue.length;
        while (count-- > 0) {
            let instanceId = queue.shift();
            let component = this.allEntities.get(instanceId);
            if (!component) {
                continue;
            }

            if (component.IsDisposed) {
                continue;
            }

            let iLateUpdateSystems: ILateUpdateSystem[] = this.typeSystems.GetSystems(component.GetClassType(), TypeOf(ILateUpdateSystem));
            if (iLateUpdateSystems == null) {
                continue;
            }

            queue.push(instanceId);

            for (let iLateUpdateSystem of iLateUpdateSystems) {
                try {
                    iLateUpdateSystem.Run(component);
                } catch (e) {
                    Log.Error(e);
                }
            }
        }
    }

    public async PublishAsync<T extends EventStruct>(scene: Scene, a: T) {
        let iEvents = this.allEvents.TryGetValue(GetTypeOf(a));
        if (!iEvents) {
            return;
        }
        let list: Promise<any>[] = [];
        for (let eventInfo of iEvents) {
            let aEvent = eventInfo.IEvent;
            if (scene.SceneType != eventInfo.SceneType && eventInfo.SceneType != SceneType.None) {
                continue;
            }
            if (!(aEvent instanceof AEvent)) {
                continue;
            }
            list.push(aEvent.Handle(a));
        }
        try {
            await Promise.all(list);
        } catch (e) {
            Log.Error(e);
        }
    }

    public Publish<T extends EventStruct>(scene: Scene, a: T) {
        let iEvents = this.allEvents.TryGetValue(GetTypeOf(a));
        if (!iEvents) {
            return;
        }
        for (let eventInfo of iEvents) {
            let aEvent = eventInfo.IEvent;
            if (scene.SceneType != eventInfo.SceneType && eventInfo.SceneType != SceneType.None) {
                continue;
            }
            if (!(aEvent instanceof AEvent)) {
                continue;
            }
            aEvent.Handle(a).then();
        }
    }

    // Invoke跟Publish的区别(特别注意)
    // Invoke类似函数，必须有被调用方，否则异常，调用者跟被调用者属于同一模块，比如MoveComponent中的Timer计时器，调用跟被调用的代码均属于移动模块
    // 既然Invoke跟函数一样，那么为什么不使用函数呢? 因为有时候不方便直接调用，比如Config加载，在客户端跟服务端加载方式不一样。比如TimerComponent需要根据Id分发
    // 注意，不要把Invoke当函数使用，这样会造成代码可读性降低，能用函数不要用Invoke
    // publish是事件，抛出去可以没人订阅，调用者跟被调用者属于两个模块，比如任务系统需要知道道具使用的信息，则订阅道具使用事件
    public Invoke<A>(args: A);
    public Invoke<A>(type: number, args: A);
    public Invoke<A, T>(args: A): T;
    public Invoke<A, T>(type: number, args: A): T
    public Invoke<A, T>(type: number | A, args?: A): T {
        let iType = 0;
        if (args && typeof type === "number") {
            iType = type;
        }
        let iArgs = null;
        if (!args) {
            iArgs = type;
        } else {
            iArgs = args;
        }
        let invokeHandlers = this.allInvokes.TryGetValue(GetTypeOf(iArgs));
        if (!invokeHandlers) {
            DumpObject(iArgs);
            throw new Error(`Invoke error: ${GetTypeOf(iArgs)}`);
        }
        let invokeHandler = invokeHandlers.TryGetValue(iType);
        if (!invokeHandler) {
            DumpObject(iArgs);
            throw new Error(`Invoke error: ${GetTypeOf(iArgs)} ${iType} `);
        }
        let aInvokeHandler = invokeHandler as AInvokeHandler1<A, T>;
        if (aInvokeHandler == null) {
            DumpObject(iArgs);
            throw new Error(`Invoke error, not AInvokeHandler: ${GetTypeOf(iArgs)} ${iType}`);
        }
        return aInvokeHandler.Handle(iArgs) as T;
    }

}